"""
文件哈希工具

用于生成文件列表的唯一哈希标识，支持结果缓存

文件唯一性识别策略：
1. 优先使用 file_content_hash（文件内容的MD5哈希）
2. 如果没有 content_hash，使用 file_url + file_size 组合
3. 如果都没有，回退到仅使用 file_url（兼容旧数据）
"""

import hashlib
import json
from typing import List, Dict, Any, Union
from pathlib import Path
from loguru import logger

from src.schemas.extraction import ExtractionFileInput


class FileHasher:
    """文件哈希生成器"""
    
    @staticmethod
    def compute_file_content_hash(file_path: Union[str, Path]) -> str:
        """
        计算文件内容的MD5哈希值（基于字节流）
        
        适用于所有文件类型（PDF、Word、Excel、图片等）
        按固定大小的块读取文件，避免大文件内存溢出
        
        Args:
            file_path: 文件路径（本地路径）
            
        Returns:
            str: MD5哈希值（十六进制字符串，32位）
            
        Example:
            >>> hash_val = FileHasher.compute_file_content_hash("document.pdf")
            >>> print(hash_val)  # "5d41402abc4b2a76b9719d911017c592"
        """
        try:
            file_path = Path(file_path)
            
            if not file_path.exists():
                logger.warning(f"文件不存在，无法计算哈希: {file_path}")
                return ""
            
            # 使用MD5算法（速度快，适合文件内容识别）
            md5_hash = hashlib.md5()
            
            # 按块读取文件，避免大文件内存溢出
            # 8KB 每块，对于大文件也能快速处理
            with open(file_path, 'rb') as f:
                for chunk in iter(lambda: f.read(8192), b''):
                    md5_hash.update(chunk)
            
            content_hash = md5_hash.hexdigest()
            logger.debug(f"文件内容哈希: {file_path.name} -> {content_hash}")
            
            return content_hash
            
        except Exception as e:
            logger.error(f"计算文件内容哈希失败 [{file_path}]: {e}", exc_info=True)
            return ""
    
    @staticmethod
    def compute_file_size(file_path: Union[str, Path]) -> int:
        """
        获取文件大小（字节）
        
        Args:
            file_path: 文件路径
            
        Returns:
            int: 文件大小（字节），失败返回0
        """
        try:
            file_path = Path(file_path)
            if file_path.exists():
                return file_path.stat().st_size
            return 0
        except Exception as e:
            logger.error(f"获取文件大小失败 [{file_path}]: {e}")
            return 0
    
    @staticmethod
    def _get_file_identity(file: ExtractionFileInput) -> str:
        """
        获取文件的唯一标识
        
        优先级策略：
        1. 如果有 file_content_hash（文件内容MD5），使用它（最可靠）
        2. 如果有 file_size，使用 file_url + file_size 组合
        3. 最后回退到仅使用 file_url（向后兼容）
        
        Args:
            file: 文件输入对象
            
        Returns:
            str: 文件唯一标识字符串
        """
        # 优先使用文件内容哈希（最可靠的方式）
        if hasattr(file, 'file_content_hash') and file.file_content_hash:
            return f"content:{file.file_content_hash}"
        
        # 其次使用 URL + 文件大小组合
        if hasattr(file, 'file_size') and file.file_size:
            return f"url_size:{file.file_url}|{file.file_size}"
        
        # 最后回退到仅使用 URL（向后兼容旧数据）
        return f"url:{file.file_url}"
    
    @staticmethod
    def compute_files_hash(
        files: List[ExtractionFileInput],
        extraction_type: str = None,
        rule_config_id: str = None
    ) -> str:
        """
        计算文件列表的哈希值
        
        基于文件内容哈希、文件类型、提取类型和规则ID生成唯一标识
        确保相同的输入总是生成相同的哈希值
        
        文件唯一性识别优先级：
        1. file_content_hash（文件内容MD5）- 最可靠
        2. file_url + file_size - 较可靠
        3. file_url - 向后兼容
        
        Args:
            files: 文件列表
            extraction_type: 提取类型（可选）
            rule_config_id: 规则配置ID（可选）
            
        Returns:
            str: SHA256哈希值（十六进制字符串）
        """
        try:
            # 构建待哈希的数据结构
            # 按文件标识排序，确保相同文件列表但顺序不同时也能得到相同哈希
            sorted_files = sorted(
                files,
                key=lambda f: (
                    FileHasher._get_file_identity(f),
                    f.file_name,
                    f.file_type.value if hasattr(f.file_type, 'value') else str(f.file_type)
                )
            )
            
            hash_data = {
                "files": [
                    {
                        # 使用文件唯一标识（优先content_hash）
                        "file_identity": FileHasher._get_file_identity(f),
                        "file_name": f.file_name,
                        "file_type": f.file_type.value if hasattr(f.file_type, 'value') else str(f.file_type)
                    }
                    for f in sorted_files
                ],
                "extraction_type": extraction_type or "",
                "rule_config_id": rule_config_id or ""
            }
            
            # 转换为JSON字符串（确保键排序）
            json_str = json.dumps(hash_data, sort_keys=True, ensure_ascii=False)
            
            # 计算SHA256哈希
            hash_obj = hashlib.sha256(json_str.encode('utf-8'))
            files_hash = hash_obj.hexdigest()
            
            # 统计使用了 content_hash 的文件数量
            content_hash_count = sum(
                1 for f in files
                if hasattr(f, 'file_content_hash') and f.file_content_hash
            )
            
            logger.debug(
                f"计算文件哈希: {files_hash[:16]}... "
                f"(共{len(files)}个文件, {content_hash_count}个使用内容哈希)"
            )
            
            return files_hash
            
        except Exception as e:
            logger.error(f"计算文件哈希失败: {e}", exc_info=True)
            # 如果哈希计算失败，返回空字符串，不影响正常流程
            return ""
    
    @staticmethod
    def compute_files_hash_from_dict(
        files: List[Dict[str, Any]],
        extraction_type: str = None,
        rule_config_id: str = None
    ) -> str:
        """
        从字典列表计算文件哈希
        
        便捷方法，用于处理原始字典数据
        
        Args:
            files: 文件字典列表
            extraction_type: 提取类型（可选）
            rule_config_id: 规则配置ID（可选）
            
        Returns:
            str: SHA256哈希值（十六进制字符串）
        """
        try:
            # 转换为ExtractionFileInput对象
            file_inputs = [
                ExtractionFileInput(**f) for f in files
            ]
            
            return FileHasher.compute_files_hash(
                files=file_inputs,
                extraction_type=extraction_type,
                rule_config_id=rule_config_id
            )
            
        except Exception as e:
            logger.error(f"从字典计算文件哈希失败: {e}", exc_info=True)
            return ""
    
    @staticmethod
    def validate_hash(files_hash: str) -> bool:
        """
        验证哈希值是否有效
        
        Args:
            files_hash: 文件哈希值
            
        Returns:
            bool: 是否有效
        """
        if not files_hash:
            return False
        
        # SHA256哈希应该是64个十六进制字符
        if len(files_hash) != 64:
            return False
        
        try:
            int(files_hash, 16)
            return True
        except ValueError:
            return False

